// shader ////////////////////////////////////////////////////////////////////////////////////////
function shader_load(gl, vs_src, fs_src) {
	// compile vertex shader
	var vs = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vs, vs_src);
	gl.compileShader(vs);
	if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
		alert('vertex shader error:' + gl.getShaderInfoLog(vs));
		return null;
	}
	// compile fragment shader
	var fs = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fs, fs_src);
	gl.compileShader(fs);
	if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
		alert('fragment shader error:' + gl.getShaderInfoLog(fs));
		return null;
	}
	// link to program
	var pgm = gl.createProgram();
	gl.attachShader(pgm, vs);
	gl.attachShader(pgm, fs);
	gl.linkProgram(pgm);
	if (!gl.getProgramParameter(pgm, gl.LINK_STATUS)) {
		alert("link shader fail...");
		return null;
	}
	return pgm;
}

// 2d ////////////////////////////////////////////////////////////////////////////////////////
function G2D(gl, w, h) {
	var gl = gl;
	var vs = '\
		attribute vec3 av_pos;\
		attribute vec4 av_col;\
		attribute vec2 av_tex;\
		uniform vec2 uv_rsl;\
		varying vec4 vv_col;\
		varying vec2 vv_tex;\
		void main() {\
			vec2 p2d = vec2(av_pos.x, av_pos.y);\
			vec2 one = p2d / uv_rsl;\
			vec2 two = one * 2.0;\
			vec2 clip = two - 1.0;\
			gl_Position = vec4(clip, av_pos.z, 1);\
			vv_col = av_col;\
			vv_tex = av_tex;\
		}';
	var fs = '\
		precision mediump float;\
		varying vec4 vv_col;\
		uniform bool ub_usetex;\
		uniform sampler2D us_tex1;\
		varying vec2 vv_tex;\
		void main() {\
			if (ub_usetex) {\
				vec4 texcol = texture2D(us_tex1, vv_tex);\
				if (texcol == vec4(0.0, 0.0, 0.0, 1.0))   { discard; }\
				gl_FragColor = texcol;\
			} else {\
				gl_FragColor = vv_col;\
			}\
		}';
	// init
	var vpw = w;
	var vph = h;
	var pgm = shader_load(gl, vs, fs);
	gl.useProgram(pgm);
	gl.uniform2f(gl.getUniformLocation(pgm, 'uv_rsl'), w, h);
	var loc_pos = gl.getAttribLocation(pgm, 'av_pos');
	var loc_col = gl.getUniformLocation(pgm, 'av_col');
	var loc_tex = gl.getAttribLocation(pgm, 'av_tex');
	var loc_usetex = gl.getUniformLocation(pgm, 'ub_usetex');
	var loc_sap1 = gl.getUniformLocation(pgm, 'us_tex1');
	
	gl.enableVertexAttribArray(loc_pos);
	gl.enableVertexAttribArray(loc_col);
	gl.uniform1i(loc_usetex, false);
	gl.uniform1i(loc_sap1, 0);
	gl.activeTexture(gl.TEXTURE0);
	gl.enable(gl.DEPTH_TEST);
	gl.clearDepth(1);

	// create buffers
	var buf_point_vert = gl.createBuffer();
	var buf_point_col = gl.createBuffer();
	var point_vert = [];
	var point_col = [];
	
	var buf_line_vert = gl.createBuffer();
	var buf_line_col = gl.createBuffer();
	var line_vert = [];
	var line_col = [];
	
	var buf_rect_vert = gl.createBuffer();
	var buf_rect_col = gl.createBuffer();
	var rect_vert = [];
	var rect_col = [];
	
	var buf_circle_vert = gl.createBuffer();
	var buf_circle_col = gl.createBuffer();
	var circle_vert = [];
	var circle_col = [];
	
	var fttex = -1;
	var ftw, fth;
	var ctp = [];   // char ascii to pos at font texture
	var buf_font_vert = gl.createBuffer();
	var buf_font_tex = gl.createBuffer();
	var font_vert = [];
	var font_tex = [];
	
	var buf_img_vert = gl.createBuffer();
	var buf_img_tex = gl.createBuffer();
	var texbuf = [];
	
	// api
	this.begin = function() {
		point_vert = [], point_col = [];
		line_vert = [], line_col = [];
		rect_vert = [], rect_col = [];
		circle_vert = [], circle_col = [];
		font_vert = [], font_tex = [];
		texbuf = [];
		
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		gl.viewport(0, 0, vpw, vph);
	}
	this.end = function() {
		if (point_vert.length > 0) {
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_point_vert);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(point_vert), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_point_col);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(point_col), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_col, 4, gl.FLOAT, false, 0, 0);
			gl.drawArrays(gl.POINTS, 0, point_vert.length / 3);
		}
		if (line_vert.length > 0) {
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_line_vert);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(line_vert), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_line_col);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(line_col), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_col, 4, gl.FLOAT, false, 0, 0);
			gl.drawArrays(gl.LINES, 0, line_vert.length / 3);
		}
		if (rect_vert.length > 0) {
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_rect_vert);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(rect_vert), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_rect_col);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(rect_col), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_col, 4, gl.FLOAT, false, 0, 0);
			gl.drawArrays(gl.TRIANGLES, 0, rect_vert.length / 3);
		}
		if (circle_vert.length > 0) {
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_circle_vert);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(circle_vert), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_circle_col);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(circle_col), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_col, 4, gl.FLOAT, false, 0, 0);
			gl.drawArrays(gl.TRIANGLES, 0, circle_vert.length / 3);
		}
		gl.enableVertexAttribArray(loc_tex);
		gl.disableVertexAttribArray(loc_col);
		gl.uniform1i(loc_usetex, true);
		for (var i in texbuf) {
			gl.bindTexture(gl.TEXTURE_2D, texbuf[i][0]);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_img_tex);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texbuf[i][2]), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_tex, 2, gl.FLOAT, false, 0, 0);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_img_vert);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texbuf[i][1]), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
			gl.drawArrays(gl.TRIANGLES, 0, texbuf[i][1].length / 3);
		}
		if (font_vert.length > 0 && fttex != -1) {
			gl.bindTexture(gl.TEXTURE_2D, fttex);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_font_tex);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(font_tex), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_tex, 2, gl.FLOAT, false, 0, 0);
			gl.bindBuffer(gl.ARRAY_BUFFER, buf_font_vert);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(font_vert), gl.STATIC_DRAW);
			gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
			gl.drawArrays(gl.TRIANGLES, 0, font_vert.length / 3);
		}
		gl.enableVertexAttribArray(loc_col);
		gl.disableVertexAttribArray(loc_tex);
		gl.uniform1i(loc_usetex, false);
	}
	this.point = function(x, y, z, c) {
		point_vert.push(x,y,z);
		point_col.push(c[0],c[1],c[2],1);
	}
	this.line = function(sx, sy, ex, ey, c, z) {
		line_vert.push(sx,sy,z, ex,ey,z);
		line_col.push(c[0],c[1],c[2],1, c[0],c[1],c[2],1);
	}
	this.rect = function(x, y, w, h, c, z) {
		rect_vert.push(x,y,z, x,y+h,z, x+w,y+h,z, x,y,z, x+w,y+h,z, x+w,y,z);
		rect_col.push(c[0],c[1],c[2],1, c[0],c[1],c[2],1, c[0],c[1],c[2],1, c[0],c[1],c[2],1, c[0],c[1],c[2],1, c[0],c[1],c[2],1);
	}
	this.circle = function(x, y, r, c, z) {
		var cut = 50;
		var per = 2 * Math.PI / cut;
		for (var i=0; i<cut; i++) {   // here is the bottleneck
			circle_vert.push(x, y, z);
			circle_col.push(c[0], c[1], c[2], 1);
			
			var ang = i * per;
			circle_vert.push(x + Math.cos(ang) * r, y + Math.sin(ang) * r, z);
			circle_col.push(c[0], c[1], c[2], 1);
			
			ang = (i + 1) * per;
			circle_vert.push(x + Math.cos(ang) * r, y + Math.sin(ang) * r, z);
			circle_col.push(c[0], c[1], c[2], 1);
		}
	}
	this.bind_texture = function(texid) {
		texbuf.push([texid, [], []]);   // texture id, vertex position buffer, texture coord buffer
	}
	this.image = function(texw, texh, rcsrc, rcdst, z) {
		var idx = texbuf.length - 1;
		var l, r, t, b;
		// vertex position data
		l = rcdst[0], b = rcdst[1], r = rcdst[2], t = rcdst[3];
		texbuf[idx][1].push(l,b,z, l,t,z, r,t,z, l,b,z, r,t,z, r,b,z);
		// texture coord data
		l = rcsrc[0] / texw, b = rcsrc[1] / texh;
		r = rcsrc[2] / texw, t = rcsrc[3] / texh;
		texbuf[idx][2].push(l,b, l,t, r,t, l,b, r,t, r,b);
	}
	/*this.image_flush = function(texid, texw, texh, rcsrc, rcdst, z) {
		gl.enableVertexAttribArray(loc_tex);
		gl.disableVertexAttribArray(loc_col);
		gl.uniform1i(loc_usetex, true);

		gl.bindTexture(gl.TEXTURE_2D, texid);

		var left, bottom, right, top;
		left = rcsrc[0] / texw, bottom = rcsrc[1] / texh;
		right = rcsrc[2] / texw, top = rcsrc[3] / texh;
		var texcod = [left,bottom, left,top, right,top, left,bottom, right,top, right,bottom];
		var texbuf = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, texbuf);
		gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcod), gl.STATIC_DRAW);
		gl.vertexAttribPointer(loc_tex, 2, gl.FLOAT, false, 0, 0);

		left = rcdst[0], bottom = rcdst[1], right = rcdst[2], top = rcdst[3];
		var vtxcod = [left,bottom,z, left,top,z, right,top,z, left,bottom,z, right,top,z, right,bottom,z];
		var vtxbuf = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, vtxbuf);
		gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vtxcod), gl.STATIC_DRAW);
		gl.vertexAttribPointer(loc_pos, 3, gl.FLOAT, false, 0, 0);
		gl.drawArrays(gl.TRIANGLES, 0, 6);

		gl.enableVertexAttribArray(loc_col);
		gl.disableVertexAttribArray(loc_tex);
		gl.uniform1i(loc_usetex, false);
	}*/
	
	this.font_create = function(url) {
		var img = new Image();
		img.src = url;
		img.onload = function() {
			fttex = gl.createTexture();
			gl.bindTexture(gl.TEXTURE_2D, fttex);
			gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
			gl.generateMipmap(gl.TEXTURE_2D);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
			gl.bindTexture(gl.TEXTURE_2D, null);
		}
		
		ftw = 256;
		fth = 256;
		for (var i=0; i<256; i++)   ctp[i]=[0,0];
		
		var i, c;
		// 'A' ~ 'Z'
		i=1;
		for (c='A'.charCodeAt(0); c<='O'.charCodeAt(0); ++c) {
			ctp[c] = [i*16, 13*16];
			++i;
		}
		i=0;
		for (c='P'.charCodeAt(0); c<='Z'.charCodeAt(0); ++c) {
			ctp[c] = [i*16, 12*16];
			++i;
		}
		// 'a' ~ 'z'
		i=1;
		for (c='a'.charCodeAt(0); c<='o'.charCodeAt(0); ++c) {
			ctp[c] = [i*16, 11*16];
			++i;
		}
		i=0;
		for (c='p'.charCodeAt(0); c<='z'.charCodeAt(0); ++c) {
			ctp[c] = [i*16, 10*16];
			++i;
		}
		// '0' ~ '9'
		i=0;
		for (c='0'.charCodeAt(0); c<='9'.charCodeAt(0); ++c) {
			ctp[c] = [i*16, 14*16];
			++i;
		}
		// ' '  '.'  ':'  ','  '('  ')'  '-'
		ctp[' '.charCodeAt(0)] = [0, 15*16];
		ctp['.'.charCodeAt(0)] = [14*16, 15*16];
		ctp[':'.charCodeAt(0)] = [10*16, 14*16];
		ctp[','.charCodeAt(0)] = [12*16, 15*16];
		ctp['('.charCodeAt(0)] = [8*16, 15*16];
		ctp[')'.charCodeAt(0)] = [9*16, 15*16];
		ctp['-'.charCodeAt(0)] = [13*16, 15*16];
	}
	this.font_draw = function(str, x, y) {
		var dtx = 0;
		var l, r, t, b;
		for (var i=0; i<str.length; i++) {
			var idx = str.charCodeAt(i);
			var sx = ctp[idx][0];
			var sy = ctp[idx][1];

			l = sx/ftw;
			r = (sx+16)/ftw;
			t = (sy+16)/fth;
			b = sy/fth;
			font_tex.push(l,b, l,t, r,t, l,b, r,t, r,b);
			
			l = x+dtx;
			r = x+dtx+16;
			t = y+16;
			b = y;
			font_vert.push(l,b,0, l,t,0, r,t,0, l,b,0, r,t,0, r,b,0);
			
			dtx += 9;
		}
	}
}
// timer ////////////////////////////////////////////////////////////////////////////////////////
function timer() {
	var tick;
	this.reset = function() {
		tick = new Date().getTime();
	}
	this.pass_ms = function() {
		return new Date().getTime() - tick;
	}
}